﻿using System.CodeDom;
using OrganizationName.CodeGeneration;

namespace OrganizationName.Factory
{
    public class LifeTimeContract : AnonymousInstance
    {
        private string ClassMemberUniqueId
        {
            get
            {
                return "_INSTANCE" + UniqueId;
            }
        }

        private string SyncRootMemberId
        {
            get
            {
                return "_SYNC" + UniqueId;
            }
        }

        private string LocalSyncRootUniqueId
        {
            get
            {
                return "_LOCAL_SYNC" + UniqueId;
            }
        }

        private string GetContractObjectUniqueId(ICodeContext codeContext)
        {
            return codeContext.ParentContext.CodeProvider.UniqueId;
        }

        private System.Type GetContractObjectConcreteType(ICodeContext codeContext)
        {
            return (codeContext.ParentContext.CodeProvider as GenericMaterial).GetConcreteType(codeContext.ParentContext);
        }

        public override void DeclarationPhase(ICodeContext codeContext)
        {
            codeContext.Class.Members.Add(new CodeMemberField(GetConcreteType(codeContext), ClassMemberUniqueId));

            base.DeclarationPhase(codeContext);

            CodeMemberField syncRoot = new CodeMemberField(typeof(object), SyncRootMemberId);
            syncRoot.InitExpression = new CodeObjectCreateExpression(typeof(object));
            codeContext.Class.Members.Add(syncRoot);

            codeContext.Statements.Add(new CodeSnippetExpression("if (" + ClassMemberUniqueId + " == null) {"));
            codeContext.Statements.Add(new CodeSnippetExpression("lock(" + SyncRootMemberId + ") {"));
            codeContext.Statements.Add(new CodeSnippetExpression("if (" + ClassMemberUniqueId + " == null) {"));

            InitializationPhase(codeContext);
            base.FinalizationPhase(codeContext);

            codeContext.Statements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression(ClassMemberUniqueId), new CodeVariableReferenceExpression(UniqueId)));

            codeContext.Statements.Add(new CodeSnippetExpression("} } }"));

            codeContext.Statements.Add(new CodeSnippetExpression("if (!" + ClassMemberUniqueId + ".IsValid) {"));

            codeContext.Statements.Add(new CodeVariableDeclarationStatement(typeof(object), LocalSyncRootUniqueId));
            codeContext.Statements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression(LocalSyncRootUniqueId), new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(ClassMemberUniqueId), "SyncRoot")));
            codeContext.Statements.Add(new CodeConditionStatement(new CodeBinaryOperatorExpression(new CodeVariableReferenceExpression(LocalSyncRootUniqueId), CodeBinaryOperatorType.IdentityEquality, new CodeSnippetExpression("null")), new CodeAssignStatement(new CodeVariableReferenceExpression(LocalSyncRootUniqueId), new CodeVariableReferenceExpression(ClassMemberUniqueId))));

            codeContext.Statements.Add(new CodeSnippetExpression("lock(" + LocalSyncRootUniqueId + ") {"));
            codeContext.Statements.Add(new CodeSnippetExpression("if (!" + ClassMemberUniqueId + ".IsValid) {"));
        }

        public override void FinalizationPhase(ICodeContext codeContext)
        {
            codeContext.Statements.Add
            (
                new CodeAssignStatement
                (
                    new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(ClassMemberUniqueId), "Instance"),
                    new CodeVariableReferenceExpression(GetContractObjectUniqueId(codeContext))
                )
            );

            codeContext.Statements.Add(new CodeSnippetExpression("} else {"));
            AppendCacheReturnStatement(codeContext, ClassMemberUniqueId);
            codeContext.Statements.Add(new CodeSnippetExpression("} } } else {"));
            AppendCacheReturnStatement(codeContext, ClassMemberUniqueId);
            codeContext.Statements.Add(new CodeSnippetExpression("}"));
        }

        private void AppendCacheReturnStatement(ICodeContext codeContext, string classMemberUniqueId)
        {
            codeContext.Statements.Add
            (
                new CodeAssignStatement
                (
                    new CodeVariableReferenceExpression(GetContractObjectUniqueId(codeContext)),
                    new CodeCastExpression
                    (
                        GetContractObjectConcreteType(codeContext),
                        new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(classMemberUniqueId), "Instance")
                    )
                )
            );
        }
    }
}
